/*
 * Copyright (c) JForum Team. All rights reserved.
 *
 * The software in this package is published under the terms of the LGPL
 * license a copy of which has been included with this distribution in the
 * license.txt file.
 *
 * The JForum Project
 * http://www.jforum.net
 */
package net.jforum.entities;

import java.awt.image.BufferedImage;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.jforum.security.RoleManager;
import net.jforum.util.Captcha;
import net.jforum.util.ConfigKeys;
import net.jforum.util.JForumConfig;

import org.apache.log4j.Logger;

import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.SessionScoped;

import com.octo.captcha.image.ImageCaptcha;

/**
 * Stores information about an user's session.
 * 
 * @author Rafael Steil
 */
@Component
@SessionScoped
public class UserSession {
	private static final Logger logger = Logger.getLogger(UserSession.class);
	private User user = new User(null);
	private RoleManager roleManager;
	private Map<Integer, Long> topicReadTime = new HashMap<Integer, Long>();
	private long lastAccessedTime;
	private long creationTime;
	private long lastVisit;
	private String sessionId;
	private HttpServletRequest request;
	private HttpServletResponse response;

	private ImageCaptcha imageCaptcha = null;
	private JForumConfig config;

	public void setResponse(HttpServletResponse response) {
		this.response = response;
	}

	/**
	 * Flag a specific topic as "read" by the user It will be ignored if the
	 * user is not logged
	 * 
	 * @param topicId
	 *            the id of the topic to mark as read
	 */
	public void markTopicAsRead(int topicId) {
		if (this.isLogged()) {
			this.topicReadTime.put(topicId, System.currentTimeMillis());
		}
	}

	public void setRequest(HttpServletRequest request) {
		this.request = request;
	}

	/**
	 * Check if the user has read a specific topic.o
	 * 
	 * @param topic
	 *            the topic. Check will be made against
	 *            <code>topic.lastPost.date</code>
	 * @return true if the topic is read or if the user is not logged.
	 */
	public boolean isTopicRead(Topic topic) {
		if (!this.isLogged()) {
			return true;
		}

		long lastVisit = this.getLastVisit();
		long postTime = topic.getLastPost().getDate().getTime();

		if (postTime <= lastVisit) {
			return true;
		}

		Long readTime = this.topicReadTime.get(topic.getId());
		return readTime != null && postTime <= readTime;
	}

	/**
	 * Check if there are unread messages in a specific forum FIXME this
	 * currently only checks for the time of the last message in the forum. A
	 * correct implementation should check all posts in the forum (while not
	 * hurting performance)
	 * 
	 * @param forum
	 *            the forum to check
	 * @return true if there are no unread messages in the forum, or if the user
	 *         is not logged
	 */
	public boolean isForumRead(Forum forum) {
		if (!this.isLogged() || forum.getTotalPosts() == 0
				|| forum.getLastPost() == null) {
			return true;
		}

		long lastVisit = this.getLastVisit();
		long postTime = forum.getLastPost().getDate().getTime();

		if (postTime <= lastVisit) {
			return true;
		}

		Long readTime = this.topicReadTime.get(forum.getLastPost().getTopic()
				.getId());
		return readTime != null && postTime <= readTime;
	}

	public void setRoleManager(RoleManager roleManager) {
		this.roleManager = roleManager;
	}

	public RoleManager getRoleManager() {
		return this.roleManager;
	}

	public HttpServletRequest getRequest() {
		return request;
	}

	public String getIp() {
		/*
		 * if(new JForumConfig().getBoolean(ConfigKeys.BLOCK_IP)) { return null;
		 * }
		 */

		// We look if the request is forwarded
		// If it is not call the older function.
		String ip = request.getHeader("X-Pounded-For");

		if (ip != null) {
			return ip;
		}

		ip = request.getHeader("x-forwarded-for");

		if (ip == null) {
			return request.getRemoteAddr();
		} else {
			// Process the IP to keep the last IP (real ip of the computer on
			// the net)
			StringTokenizer tokenizer = new StringTokenizer(ip, ",");

			// Ignore all tokens, except the last one
			for (int i = 0; i < tokenizer.countTokens() - 1; i++) {
				tokenizer.nextElement();
			}

			ip = tokenizer.nextToken().trim();

			if (ip.equals("")) {
				ip = null;
			}
		}

		// If the ip is still null, we put 0.0.0.0 to avoid null values
		if (ip == null) {
			ip = "0.0.0.0";
		}

		return ip;
	}

	public User getUser() {
		return this.user;
	}

	public void setUser(User user) {
		this.user = user;

		if (user == null) {
			try {
				throw new RuntimeException(
						"userSession.setUser with null value. See the stack trace for more information about the call stack. Session ID: "
								+ this.sessionId);
			} catch (RuntimeException e) {
				Writer writer = new StringWriter();
				PrintWriter printWriter = new PrintWriter(writer);
				e.printStackTrace(printWriter);
				logger.warn(writer.toString());
			}
		}
	}

	/**
	 * Gets user's session start time
	 * 
	 * @return Start time in miliseconds
	 */
	public long getCreationTime() {
		return this.creationTime;
	}

	public void setCreationTime(long start) {
		this.creationTime = start;
		this.lastAccessedTime = start;
		this.lastVisit = start;
	}

	/**
	 * Gets user's last visit time
	 * 
	 * @return Time in miliseconds
	 */
	public long getLastAccessedTime() {
		return this.lastAccessedTime;
	}

	public Date getLastAccessedDate() {
		return new Date(this.getLastAccessedTime());
	}

	/**
	 * @return the lastVisit
	 */
	public long getLastVisit() {
		return this.lastVisit;
	}

	/**
	 * @return the lastVisit as a date
	 */
	public Date getLastVisitDate() {
		return new Date(this.lastVisit);
	}

	/**
	 * @param lastVisit
	 *            the lastVisit to set
	 */
	public void setLastVisit(long lastVisit) {
		this.lastVisit = lastVisit;
	}

	/**
	 * Updates this instance with the last accessed time of the session
	 */
	public void ping() {
		this.lastAccessedTime = System.currentTimeMillis();
	}

	/**
	 * Gets the session id related to this user session
	 * 
	 * @return A string with the session id
	 */
	public String getSessionId() {
		return this.sessionId;
	}

	public void setSessionId(String sessionId) {
		this.sessionId = sessionId;
	}

	public boolean isBot() {
		return false;
	}

	/**
	 * Makes the user session anonymous
	 */
	public void becomeAnonymous(int anonymousUserId) {
		User user = new User();
		user.setId(anonymousUserId);
		this.setUser(user);
		setAttribute(ConfigKeys.LOGGED, "0");
	}

	public void becomeLogged() {
		this.setAttribute(ConfigKeys.LOGGED, "1");
	}

	public boolean isLogged() {
		return "1".equals(this.getAttribute(ConfigKeys.LOGGED));
	}

	/**
	 * Gets a cookie by its name.
	 * 
	 * @param name
	 *            The cookie name to retrieve
	 * @return The <code>Cookie</code> object if found, or <code>null</code>
	 *         oterwhise
	 */
	public Cookie getCookie(String name) {
		Cookie[] cookies = request.getCookies();

		if (cookies != null) {
			for (Cookie c : cookies) {
				if (c.getName().equals(name)) {
					return c;
				}
			}
		}

		return null;
	}

	/**
	 * Add or update a cookie. This method adds a cookie, serializing its value
	 * using XML.
	 * 
	 * @param name
	 *            The cookie name.
	 * @param value
	 *            The cookie value
	 */
	public void addCookie(String name, String value) {
		int maxAge = 3600 * 24 * 365;

		if (value == null) {
			maxAge = 0;
			value = "";
		}

		Cookie cookie = new Cookie(name, value);
		cookie.setMaxAge(maxAge);
		cookie.setPath("/");

		response.addCookie(cookie);
	}

	/**
	 * Removes a cookie
	 * 
	 * @param name
	 *            the name of the cookie to remove
	 */
	public void removeCookie(String name) {
		this.addCookie(name, null);
	}

	public void setAttribute(String name, Object value) {
		request.getSession().setAttribute(name, value);
	}

	public Object getAttribute(String name) {
		return request.getSession().getAttribute(name);
	}

	/**
	 * Convert this instance to a {@link Session}
	 * 
	 * @return
	 */
	public Session asSession() {
		Session session = new Session();

		session.setUserId(this.user.getId());
		session.setIp(this.getIp());
		session.setStart(new Date(this.getCreationTime()));
		session.setLastAccessed(new Date(this.getLastAccessedTime()));
		session.setLastVisit(new Date(this.getLastVisit()));

		return session;
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	// @Override
	public boolean eequals(Object o) {
		if (o == this) {
			return true;
		}

		if (!(o instanceof UserSession)) {
			return false;
		}

		return this.getSessionId().equals(((UserSession) o).getSessionId());
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return this.getSessionId().hashCode();
	}

	/**
	 * @param time
	 */
	public void setLastAccessedTime(long time) {
		this.lastAccessedTime = time;
	}

	// add by dongguoh 2014-2-14

	/**
	 * Validate the captcha response of user
	 * 
	 * @param userResponse
	 *            String the captcha response from user
	 * @return boolean true if the answer is valid, otherwise return false
	 */
	public boolean validateCaptchaResponse(String userResponse) {
		if ((this.config.getBoolean(ConfigKeys.CAPTCHA_REGISTRATION) || this.config.getBoolean(ConfigKeys.CAPTCHA_POSTS))
				&& this.imageCaptcha != null) {

			if (this.config.getBoolean(ConfigKeys.CAPTCHA_IGNORE_CASE)) {
				userResponse = userResponse.toLowerCase();
			}

			boolean result = this.imageCaptcha.validateResponse(userResponse).booleanValue();
			this.destroyCaptcha();
			return result;
		}
		return true;
	}

	/**
	 * Destroy the current captcha validation is done
	 * 
	 */
	public void destroyCaptcha() {
		this.imageCaptcha = null;
	}

	/**
	 * Get the captcha image to challenge the user
	 * 
	 * @return BufferedImage the captcha image to challenge the user
	 */
	public BufferedImage getCaptchaImage() {
		if (this.imageCaptcha == null) {
			return null;
		}
		return (BufferedImage) this.imageCaptcha.getChallenge();
	}

	/**
	 * create a new image captcha
	 * 
	 */
	public void createNewCaptcha(JForumConfig config) {
		this.config = config;
		this.destroyCaptcha();
		this.imageCaptcha = Captcha.getInstance().getNextImageCaptcha();
	}

	public HttpServletResponse getResponse() {
		return this.response;
	}

}
